import { Document, Element, ChildNode } from 'parse5/dist/tree-adapters/default.js'

interface ChildInfo {
    path: string
    node: ChildNode
}

function sortValues (value: string): string {
    const arr = value.trim().split(/\s+/)
    arr.sort()
    return arr.join(' ')
}

function normalizeClassNames (classNames: string): string {
    const arr = classNames.trim().split(/\s+/)
    arr.sort()
    for (let i = 0, len = arr.length; i < len; i++) {
        arr[i] = '\\b' + arr[i] + '\\b'
    }
    return '.*' + arr.join('.*') + '.*'
}

function normalize (childPath: string): string {
    const parr = childPath.split(';')
    for (let i = 0, len = parr.length; i < len; i++) {
        const cp = parr[i]

        if (cp === '*') {
            parr[i] = '.*'
        } else {
            const cpRules = cp.split(',')
            // finally: #id,.class,<node>
            cpRules.sort()

            for (let j = 0, jlen = cpRules.length; j < jlen; j++) {
                const cpRule = cpRules[j]
                if (cpRule === '*') {
                    cpRules[j] = '.*'
                } else if (cpRule.startsWith('.')) {
                    const classNames = normalizeClassNames(cpRule.substring(1))
                    cpRules[j] = '\\.' + classNames
                } else if (cpRule.startsWith('<') && cpRule.endsWith('>')) {
                    cpRules[j] = cpRule.toLowerCase()
                }
            }
            let newCP = cpRules.join(',')
            const br = cpRules[0]
            if (!br.startsWith('#')) {
                newCP = '(?:.+,)?' + newCP
            }
            const er = cpRules[cpRules.length - 1]
            if (!er.startsWith('<') || !er.endsWith('>')) {
                newCP += '(?:,.+)?'
            }
            parr[i] = newCP
        }
    }
    return parr.join(';')
}

function getNodeDesc (node: ChildNode): string {
    const rules: string[] = []
    rules.push('<' + node.nodeName.toLowerCase() + '>')
    const ce = node as unknown as Element
    if (ce.attrs !== undefined) {
        for (const attr of ce.attrs) {
            if (attr.name === 'class') {
                rules.push('.' + sortValues(attr.value))
            } else if (attr.name === 'id') {
                rules.push('#' + attr.value.trim())
            }
        }
    }
    rules.sort()
    return rules.join(',')
}

function collectChildren (node: ChildNode, trace: string, target: RegExp, out: ChildInfo[]): void {
    const desc = getNodeDesc(node)
    if (trace !== '') trace += ';'
    trace += desc

    if (trace.match(target) != null) {
        out.push({ path: trace, node })
    } else {
        const nodeAlias = node as Element
        if (nodeAlias.childNodes !== undefined) {
            for (const child of nodeAlias.childNodes) {
                collectChildren(child, trace, target, out)
            }
        }
    }
}

export default function findChild (doc: Document, childPath: string): ChildNode[] {
    childPath = normalize(childPath)
    const target = new RegExp(childPath)
    // console.log('target', target)
    const childs: ChildInfo[] = []
    for (const child of doc.childNodes) {
        collectChildren(child, '', target, childs)
    }
    const out: ChildNode[] = []
    for (const c of childs) {
        out.push(c.node)
    }
    return out
}
